package com.woniuxy.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.woniuxy.entity.Product;
import com.woniuxy.mapper.ProductMapper;
import com.woniuxy.service.ProductService;
import io.seata.spring.annotation.GlobalTransactional;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 马宇航
 * @Todo: TODO
 * @DateTime: 25/07/31/星期四 10:48
 * @Component: 成都蜗牛学苑
 **/
@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    ProductMapper productMapper;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public Product getProductById(Integer pid) {
        return productMapper.getById(pid);
    }

    @Override
    public void updateProduct(Integer pid, Integer num){
        //synchronized是线程需要拿到ProductServiceImpl.class对象的锁，而这个对象再单机中是唯一的。所以可以锁住
        //如果写的是this能不能锁住？
        //利用redisson内部封装的方法，获取一个锁，setnx写入redis
//        RLock lock = redissonClient.getLock("product::lock");
        //方案2：手写分布式锁:flag为true 表示就拿到锁，说明写入成功
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("product::lock", 1, 800, TimeUnit.MILLISECONDS);
        //锁的一个ttl时长，为什么要这个？避免死锁。
        try {
            //flag = false的线程，说明拿锁失败
            while (!flag) {
                //自旋：直到拿到了这个锁，且，flag死循环中，有操作内存的业务，就不需要加volatile了，本身就有可见性。
                //过期时间是解决死锁的。
                flag = redisTemplate.opsForValue().setIfAbsent("product::lock", 1, 800, TimeUnit.MILLISECONDS);
            }
            //有一种情况，800ms无法执行完下面的业务 看门狗机制，解决线程未执行完，但是锁释放了的情况
            //看门狗机制
            Thread thread = new Thread(() -> {
                try {
                    //等600ms后，延长ttl锁的过期时间,600ms后主线程还存在，则续期1s，让主线程继续持有锁
                    Thread.sleep(600);
                    redisTemplate.expire("product::lock",1,TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }, "watchdog");
            //它必须是守护线程，才允许显示的new出来
            //主线程消失，守护线程也解决，GC执行的线程都是守护线程。
            thread.setDaemon(true);
            thread.start();
            //查询--->save更新，只能一个线程执行。
            //1.商品想要扣减数量，先查询商品
            Product product = productMapper.getReferenceById(pid);
            //2.判断商品库存是否充足
            if (product.getStock() < num) {
                throw new RuntimeException("库存不足");
            }
            //3.扣减库存，用业务减掉数量（而不是通过sql扣减数量）
            product.setStock(product.getStock() - num);
            //4.更新数据库

            productMapper.save(product);
        }finally {
            //必须再finally中释放锁
            redisTemplate.delete("product::lock");
        }
        //5.模拟异常，看是否两个服务都回滚了。
        int i = 1/0;
    }

    @Override
    public List<Product> findByStore(Integer startStock, Integer endStock, Integer startPrice, Integer endPrice) {
        return productMapper.findAllByStockBetweenAndPriceBetween(startStock, endStock, startPrice, endPrice);
    }
}
